#include <stdio.h>
#include <cstring>
#include <string>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <esp_timer.h>
#include "esp_log.h"
#include "tm1638.h"
#include "symbols.h"

const char *log_debug = "TM1638_Debug";

void TM1638::sendByte(uint8_t byte)
{
    for (int i = 0; i < 8; i++)
    {
        gpio_set_level(m_pinClk, 0);
        delay();
        gpio_set_level(m_pinDta, byte & 0x01);
        byte >>= 1;
        delay();
        gpio_set_level(m_pinClk, 1);
        delay();
    }
    // 数据发送完毕拉低clk。这个不是必须的。
    gpio_set_level(m_pinClk, 0);
    delay();
}

uint8_t TM1638::readByte()
{
    uint8_t data = 0x00;
    for (int i = 0; i < 8; i++)
    {
        uint8_t mask = 1 << i;
        gpio_set_level(m_pinClk, 0);
        delay();
        gpio_set_level(m_pinClk, 1);
        delay();
        if (gpio_get_level(m_pinDta))
        {
            data |= mask;
        }
    }
    // 数据发送完毕拉低clk。这个不是必须的。
    gpio_set_level(m_pinClk, 0);
    delay();
    return data;
}

void TM1638::sendCommand(uint8_t cmd)
{
    stbLow();
    sendByte(cmd);
    // 命令发送完毕不一定都把stb拉高，比如发送数据就可以接下来直接发送数据，不需要在命令和数据之间拉高stb一次。
}

void TM1638::sendAddrData(uint8_t addr, uint8_t data)
{
    stbLow();
    sendByte(addr);
    sendByte(data);
    stbHight();
}

void TM1638::scanKey()
{
    if (xSemaphoreTake(m_busMux, portMAX_DELAY) == pdTRUE)
    {
        stbLow();
        sendCommand(CMD_RD_KEY);
        delay();
        // 设置DIO口为输入
        gpio_set_direction(m_pinDta, GPIO_MODE_INPUT);
        for (int i = 0; i < 4; i++)
        {
            m_keyBuf.buf[i] = readByte();
        }
        delay();
        stbHight();
        gpio_set_direction(m_pinDta, GPIO_MODE_OUTPUT);
        xSemaphoreGive(m_busMux);
    }
}

void TM1638::getImage(const char *str, uint8_t *image, size_t arrSize)
{
    int len = strlen(str);
    for (int i = 0; (i < arrSize) && (i < len); i++)
    {
        if (str[i] < sizeof(ascii_symbols) / sizeof(ascii_symbols[0]))
        {
            image[i] = ascii_symbols[(unsigned char)str[i]];
        }
        else // str中含有超过ascii码的字符。
        {
            image[i] = 0x00;
        }
    }
}

void TM1638::convertCathode2Anode()
{
    uint8_t dispCathodeData[m_kSegCount]; // 临时存储显示数据.
    std::copy(std::begin(m_dispDatas), std::end(m_dispDatas), std::begin(dispCathodeData));

    for (int cathode_idx = 0; cathode_idx < m_kSegCount; cathode_idx++)
    {
        // 当前处理的共阳极数据的屏蔽字：该位表示原来是第几个共阴极数据。
        uint8_t anode_mask = 1 << cathode_idx;

        // 处理第cathode_idx个的共阴极数据，使之散布在第cathode_idx位共阳极寄存器中。
        for (int i = 0; i < m_kSegCount; i++)
        {
            // 当前处理的共阴极数据的屏蔽字：从第0到第7位
            uint8_t cathode_mask = 1 << i;
            if (dispCathodeData[cathode_idx] & cathode_mask)
            {
                m_dispDatas[i] |= anode_mask;
            }
            else
            {
                m_dispDatas[i] &= ~anode_mask;
            }
        }
    }
}

// pv是传入的对象
/*
void TM1638::keyTaskStaticTrampoline(void *param)
{
    auto *self = static_cast<TM1638 *>(param); // 拿回 this
    self->keyTaskLoop();                       // 真正的键盘任务处理函数，无限循环.
}
*/

// 真正的键盘任务处理函数
/*
void TM1638::keyTaskLoop()
{
    // 初始化键盘环形缓冲区和读写索引
    for (int i = 0; i < KEY_BUF_SIZE; i++)
    {
        m_keyFifo[i].key_id = 0;
        m_keyFifo[i].type = KEY_EV_INIT;
    }
    m_keyRi = m_keyWi = 0;

    uint8_t key_state[KEY_NUM] = {0}; // 按键状态：0=松 1=短按 2=长按 3=重复长按
    uint16_t key_cnt[KEY_NUM] = {0};  // 按键次数计数器

    while (1)
    {
        scanKey();
        // 把keybuf转换为32位数据raw
        m_raw = m_keyBuf.buf[0] | m_keyBuf.buf[1] << 8 | m_keyBuf.buf[2] << 16 | m_keyBuf.buf[3] << 24;

        for (int i = 0; i < KEY_NUM; ++i)
        {
            bool down = m_raw & (1U << i); // 测试第i位的按键是否被按下。
            if (down && key_state[i] == 0) // 刚被按下，即检测到上升沿。
            {
                key_state[i] = 1;
                key_cnt[i] = 0;
                // 如果队列未满，则把事件入队
                uint8_t next = (m_keyWi + 1) % KEY_BUF_SIZE;
                if (next != m_keyRi)
                {
                    m_keyFifo[m_keyWi].key_id = i;
                    m_keyFifo[m_keyWi].type = KEY_EV_PRESS;
                    m_keyWi = next;
                }
            }
            else if (!down && key_state[i]) // 刚被释放，也即检测到下降沿.
            {
                key_state[i] = 0;
                // 如果队列未满，则把事件入队
                uint8_t next = (m_keyWi + 1) % KEY_BUF_SIZE;
                if (next != m_keyRi)
                {
                    m_keyFifo[m_keyWi].key_id = i;
                    m_keyFifo[m_keyWi].type = KEY_EV_RELEASE;
                    m_keyWi = next;
                }
            }
            else if (down && key_state[i]) // 连续检测到按下。
            {
                key_cnt[i]++;
                if (key_cnt[i] == LONG_TICKS) // 长按
                {
                    key_state[i] = 2;
                    // 如果队列未满，则把事件入队
                    uint8_t next = (m_keyWi + 1) % KEY_BUF_SIZE;
                    if (next != m_keyRi)
                    {
                        m_keyFifo[m_keyWi].key_id = i;
                        m_keyFifo[m_keyWi].type = KEY_EV_LONG;
                        m_keyWi = next;
                    }
                }
                else if (key_cnt[i] > LONG_TICKS &&
                         (key_cnt[i] - LONG_TICKS) % REPEAT_TICKS == 0 &&
                         key_state[i] != 3) // 多次重复长按。
                {
                    key_state[i] = 3;
                    // 如果队列未满，则把事件入队
                    uint8_t next = (m_keyWi + 1) % KEY_BUF_SIZE;
                    if (next != m_keyRi)
                    {
                        m_keyFifo[m_keyWi].key_id = i;
                        m_keyFifo[m_keyWi].type = KEY_EV_REPEAT;
                        m_keyWi = next;
                    }
                }
            }
        }

        vTaskDelay(pdMS_TO_TICKS(SCAN_PERIOD_MS));
    }
}

*/

TM1638::TM1638(gpio_num_t pin_stb, gpio_num_t pin_clk, gpio_num_t pin_dta, uint8_t brightness, const segLayout_t *p, bool anode)
{
    // 初始化模块的数码管布局映射
    for (int i = 0; i < m_kSegCount; i++)
    {
        m_segLayout[i] = p->idx[i];
    }

    m_pinStb = pin_stb;
    m_pinClk = pin_clk;
    m_pinDta = pin_dta;
    m_brightness = brightness;

    // 如果anode=true则是共阳极数码管
    m_useCommonAnode = anode;

    // 初始化通讯线
    gpio_reset_pin(m_pinClk);
    gpio_reset_pin(m_pinDta);
    gpio_reset_pin(m_pinStb);
    gpio_set_direction(m_pinClk, GPIO_MODE_OUTPUT);
    gpio_set_direction(m_pinDta, GPIO_MODE_OUTPUT);
    gpio_set_direction(m_pinStb, GPIO_MODE_OUTPUT);

    // 为了避免在初始化DIO期间发送一个错误的数据，把clk拉低。
    gpio_set_level(m_pinStb, 1); // 非选通
    delay();
    gpio_set_level(m_pinClk, 0);
    delay();
    gpio_set_level(m_pinDta, 1);
    delay();
    gpio_set_level(m_pinClk, 1); // ?? 为何又要拉高
    delay();
    // 至此，TM1637准备好了通讯，stb、clk和dta都是高电平。

    // 创建总线互斥锁
    m_busMux = xSemaphoreCreateMutex();
}

TM1638::~TM1638()
{
    if (m_busMux != NULL)
        vSemaphoreDelete(m_busMux);
}

void TM1638::setSegmentAuto(const int8_t dispReg_start_addr, const uint8_t *data, int arrQty)
{
    if (data == NULL || dispReg_start_addr < 0 || dispReg_start_addr > 0x0F || arrQty <= 0 || arrQty > 16)
    {
        ESP_LOGD(log_debug, "set_segment_auto:参数有误。");
        return;
    }

    if (xSemaphoreTake(m_busMux, portMAX_DELAY) == pdTRUE)
    {
        sendCommand(CMD_ADD_AUTO);
        stbHight();
        sendCommand(dispReg_start_addr | CMD_START_ADDR);
        for (int i = 0; i < arrQty; i++)
        {
            sendByte(data[i]);
        }
        stbHight();
        sendCommand(m_brightness | CMD_BRIGHT_ON);
        stbHight();
        xSemaphoreGive(m_busMux);
    }
}

void TM1638::setSegmentFixed(const int8_t dispReg_addr, const uint8_t data)
{
    if (dispReg_addr < 0 || dispReg_addr > 0x0F)
    {
        ESP_LOGD(log_debug, "set_segment_fixed: 参数有误。");
        return;
    }

    if (xSemaphoreTake(m_busMux, portMAX_DELAY) == pdTRUE)
    {
        sendCommand(CMD_ADD_FIX);
        stbHight();
        delay();

        sendAddrData(dispReg_addr | CMD_START_ADDR, data);

        sendCommand(m_brightness | 0x88);
        stbHight();
        delay();
        xSemaphoreGive(m_busMux);
    }
}

void TM1638::dispNumberOneSegment(const int8_t segment_idx, const uint8_t num, const bool dot)
{
    if (segment_idx < 0 || segment_idx >= m_kSegCount || num > 9)
    {
        ESP_LOGI("DEBUG", "segment_idx = %d", segment_idx);
        return;
    }

    // 获取num的ascii码
    int8_t num_char = num + '0';
    m_dispDatas[m_segLayout[segment_idx]] = ascii_symbols[num_char];
    if (dot)
    {
        m_dispDatas[m_segLayout[segment_idx]] = m_dispDatas[m_segLayout[segment_idx]] | 0x80;
    }
    if (m_useCommonAnode)
        convertCathode2Anode();

    // 使用地址自动增加模式显示
    // 如果是共阴极数码管，只需要使用固定地址模式显示即可；
    // 但如果是共阳极数码管，由于num分散在多个显示寄存器里面，只能使用自动地址增加模式显示。
    // 或者使用多个固定地址模式，这会增加数据量。
    // TM1638支持多个地址数据对的通讯帧，但本项目不提供多个连续的地址数据对，只提供一个地址数据对显示功能。
    uint8_t data[m_kSegCount * 2] = {0x00}; // 发往TM1638的数据。
    for (int i = 0; i < m_kSegCount; i++)
    {
        data[i * 2] = m_dispDatas[i];
    }

    setSegmentAuto(0, data, m_kSegCount * 2);
}

void TM1638::dispString(const char *str, int dot_idx, bool left)
{
    if (str == NULL)
    {
        clear();
        return;
    }

    int len = strlen(str);
    int seg_start_idx = 0; // 处理数码管时的开始索引。

    // 如果字符串长度len比数码管位数多，则都按照左对齐处理。
    if (len >= m_kSegCount)
    {
        left = true;
    }

    // 初始化ascii_data处理的开始索引。
    if (left)
    {
        seg_start_idx = 0;
    }
    else
    {
        seg_start_idx = m_kSegCount - len;
    }

    // 取得关于str的映像.
    uint8_t image[12] = {0};
    getImage(str, image, sizeof(image));
    // 如果指定了小数点位置，在置位该位的bit7
    if (dot_idx >= 0 && dot_idx < len)
    {
        image[dot_idx] |= 0x80;
    }

    // 截取待显示的字符串信息。
    for (int seg_idx = seg_start_idx, str_idx = 0; (seg_idx < m_kSegCount) && (str_idx < len); seg_idx++, str_idx++)
    {
        m_dispDatas[m_segLayout[seg_idx]] = image[str_idx];
    }

    // 如果是共阳极，则需要转换
    if (m_useCommonAnode)
    {
        convertCathode2Anode();
    }
    // 使用地址自动增加模式显示
    uint8_t data[m_kSegCount * 2] = {0x00}; // 发往TM1638的数据。
    for (int i = 0; i < m_kSegCount; i++)
    {
        data[i * 2] = m_dispDatas[i];
    }

    setSegmentAuto(0, data, m_kSegCount * 2);
}

void TM1638::dispStringSlide(const char *str, int dot_idx)
{
    if (str == NULL)
    {
        clear();
        return;
    }

    int len = strlen(str);
    // 取得关于str的映像，最多可以支持12个字符的滑动显示。
    // 对于数码管显示来说，显示过长的字符串没有意义，这里主要是为了显示数字准备。
    uint8_t image[12] = {0};
    getImage(str, image, sizeof(image));
    // 如果指定了小数点位置，在置位该为的bit7
    if (dot_idx >= 0 && dot_idx < len)
    {
        image[dot_idx] |= 0x80;
    }

    if (len > sizeof(image))
        len = sizeof(image);

    for (int win_end = 0, win_start = (win_end - m_kSegCount + 1);
         win_start < len;
         win_end++, win_start = (win_end - m_kSegCount + 1))
    {
        for (int data_idx = 0, win_idx = win_start; (data_idx < m_kSegCount) && (win_idx <= win_end); data_idx++, win_idx++)
        {
            if (win_idx < 0 || win_idx >= len)
            {
                m_dispDatas[m_segLayout[data_idx]] = 0x00;
            }
            else
            {
                m_dispDatas[m_segLayout[data_idx]] = image[win_idx];
            }
        }

        if (m_useCommonAnode)
            convertCathode2Anode();

        // 使用地址自动增加模式显示
        uint8_t data[m_kSegCount * 2] = {0x00}; // 发往TM1638的数据。
        for (int i = 0; i < m_kSegCount; i++)
        {
            data[i * 2] = m_dispDatas[i];
        }

        setSegmentAuto(0, data, m_kSegCount * 2);
        vTaskDelay(pdMS_TO_TICKS(200));
    }
}

void TM1638::dispNum(const int num, bool slide)
{
    std::string s = std::to_string(num);
    resetReg();
    dispString(s.c_str(), -1, slide);
}

void TM1638::dispNum(const double num, int precision, bool slide)
{
    char str[12] = {0x00};
    // 把num转换为字符串表示的数字.
    snprintf(str, sizeof(str), "%.*f", precision, num); // 结果只最多保留6-7位小数。

    // 查找小数点位置
    char *ptr = strchr(str, '.');
    int dotIdx = -1; // 小数点在字符串中的索引。
    if (ptr != NULL)
    {
        dotIdx = ptr - str;
        // 把小数点删除，并把之后的所有字符前移一位。
        while (*ptr != '\0')
        {
            *ptr = *(ptr + 1);
            ptr++;
        }
    }

    resetReg();
    // 显示数据
    if (slide)
        dispStringSlide(str, dotIdx - 1);
    else
        dispString(str, dotIdx - 1);
}

void TM1638::clear()
{
    uint8_t data[m_kSegCount * 2] = {0};
    resetReg();
    setSegmentAuto(0, data, m_kSegCount * 2);
}

/*
bool TM1638::getKeyEvent(key_event_t *ev)
{
    if (m_keyRi != m_keyWi)
    {
        // ESP_LOGI("ev", "key_ri= %d, key_wi= %d", key_ri, key_wi);
        ev->key_id = m_keyFifo[m_keyRi].key_id;
        ev->type = m_keyFifo[m_keyRi].type;
        m_keyRi = (m_keyRi + 1) % KEY_BUF_SIZE;
        return true;
    }
    else
        return false;
}
*/

/*
bool TM1638::startKeyTask(UBaseType_t prio, BaseType_t core)
{
    // 如果已经激活，直接退出，避免重复。
    if (m_keyTaskHandle != nullptr)
        return true;

    // 创建键盘任务
    if (xTaskCreatePinnedToCore(
            keyTaskStaticTrampoline, // 跳板函数
            "TM1638_Key",            // 名字
            2048,                    // 栈大小
            this,                    // **把 this 当 param 传进去**
            prio,
            &m_keyTaskHandle,
            core) != pdPASS)
    {
        m_keyTaskHandle = nullptr;
        return false;
    }
    else
        return true;
}
*/